
#include "GFX_disLCD.h"

namespace GFX {

#define LCD_swap(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) ///< No-temp-var swap operation

GFX_disLCD::GFX_disLCD(GFX_PortBase &lcd, uint16_t w, uint16_t h) //
    : GFX_dis(w, h), GFX_port(lcd), WIDTH(w), HEIGHT(h) {}

GFX_disLCD::~GFX_disLCD() {}

/*!
    @brief  Draw a vertical line with a width and color. Used by public method
   drawFastHLine,drawFastVLine
        @param x
                   Leftmost column -- 0 at left to (screen width - 1) at right.
        @param __y
                   Row of display -- 0 at top to (screen height -1) at bottom.
        @param __h height of the line in pixels
        @param color
                   Line color, one of: BLACK, WHITE or
   INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void GFX_disLCD::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) {

    if ((x >= 0) && (x < WIDTH)) { // X coord in bounds?
        if (__y < 0) {             // Clip top
            __h += __y;
            __y = 0;
        }
        if ((__y + __h) > HEIGHT) { // Clip bottom
            __h = (HEIGHT - __y);
        }
        if (__h > 0) { // Proceed only if height is now positive
            // this display doesn't need ints for coordinates,
            // use local byte registers for faster juggling
            uint8_t y = __y, h = __h;
            uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x];

            // do the first partial byte, if necessary - this requires some masking
            uint8_t mod = (y & 7);
            if (mod) {
                // mask off the high n bits we want to set
                mod = 8 - mod;
                // note - lookup table results in a nearly 10% performance
                // improvement in fill* functions
                // uint8_t mask = ~(0xFF >> mod);
                static const uint8_t PROGMEM premask[8] = {0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE};
                uint8_t mask                            = pgm_read_byte(&premask[mod]);
                // adjust the mask if we're not going to reach the end of this byte
                if (h < mod)
                    mask &= (0XFF >> (mod - h));

                switch (color) {
                    case WHITE:
                        *pBuf |= mask;
                        break;
                    case BLACK:
                        *pBuf &= ~mask;
                        break;
                    case INVERSE:
                        *pBuf ^= mask;
                        break;
                }
                pBuf += WIDTH;
            }

            if (h >= mod) { // More to go?
                h -= mod;
                // Write solid bytes while we can - effectively 8 rows at a time
                if (h >= 8) {
                    if (color == INVERSE) {
                        // separate copy of the code so we don't impact performance of
                        // black/white write version with an extra comparison per loop
                        do {
                            *pBuf ^= 0xFF; // Invert byte
                            pBuf += WIDTH; // Advance pointer 8 rows
                            h -= 8;        // Subtract 8 rows from height
                        } while (h >= 8);
                    } else {
                        // store a local value to work with
                        uint8_t val = (color != BLACK) ? 255 : 0;
                        do {
                            *pBuf = val;   // Set byte
                            pBuf += WIDTH; // Advance pointer 8 rows
                            h -= 8;        // Subtract 8 rows from height
                        } while (h >= 8);
                    }
                }

                if (h) { // Do the final partial byte, if necessary
                    mod = h & 7;
                    // this time we want to mask the low bits of the byte,
                    // vs the high bits we did above
                    // uint8_t mask = (1 << mod) - 1;
                    // note - lookup table results in a nearly 10% performance
                    // improvement in fill* functions
                    static const uint8_t PROGMEM postmask[8] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F};
                    uint8_t mask                             = pgm_read_byte(&postmask[mod]);
                    switch (color) {
                        case WHITE:
                            *pBuf |= mask;
                            break;
                        case BLACK:
                            *pBuf &= ~mask;
                            break;
                        case INVERSE:
                            *pBuf ^= mask;
                            break;
                    }
                }
            }
        } // endif positive height
    } // endif x in bounds
}

/*!
    @brief  Draw a horizontal line with a width and color. Used by public
   methods drawFastHLine,drawFastVLine
        @param x
                   Leftmost column -- 0 at left to (screen width - 1) at right.
        @param y
                   Row of display -- 0 at top to (screen height -1) at bottom.
        @param w
                   Width of line, in pixels.
        @param color
               Line color, one of: BLACK, WHITE or
   INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void GFX_disLCD::drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color) {
    if ((y >= 0) && (y < HEIGHT)) { // Y coord in bounds?
        if (x < 0) {                // Clip left
            w += x;
            x = 0;
        }
        if ((x + w) > WIDTH) { // Clip right
            w = (WIDTH - x);
        }
        if (w > 0) { // Proceed only if width is positive
            uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x], mask = 1 << (y & 7);
            switch (color) {
                case WHITE:
                    while (w--) {
                        *pBuf++ |= mask;
                    };
                    break;
                case BLACK:
                    mask = ~mask;
                    while (w--) {
                        *pBuf++ &= mask;
                    };
                    break;
                case INVERSE:
                    while (w--) {
                        *pBuf++ ^= mask;
                    };
                    break;
            }
        }
    }
}

void GFX_disLCD::writePixelPreclipped(int16_t x, int16_t y, uint16_t color) {
    if ((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
        // Pixel is in-bounds. Rotate coordinates if needed.
        switch (getRotation()) {
            case 1:
                LCD_swap(x, y);
                x = WIDTH - x - 1;
                break;
            case 2:
                x = WIDTH - x - 1;
                y = HEIGHT - y - 1;
                break;
            case 3:
                LCD_swap(x, y);
                y = HEIGHT - y - 1;
                break;
        }
        switch (color) {
            case WHITE:
                buffer[x + (y / 8) * WIDTH] |= (1 << (y & 7));
                break;
            case BLACK:
                buffer[x + (y / 8) * WIDTH] &= ~(1 << (y & 7));
                break;
            case INVERSE:
                buffer[x + (y / 8) * WIDTH] ^= (1 << (y & 7));
                break;
        }
    }
}

void GFX_disLCD::fillScreen(uint16_t color) { memset(buffer, BLACK == color ? 0 : 0xFF, _width * ((_height + 7) / 8)); }

void GFX_disLCD::writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
    bool bSwap = false;
    switch (getRotation()) {
        case 1:
            // 90 degree rotation, swap x & y for rotation, then invert x
            bSwap = true;
            LCD_swap(x, y);
            x = WIDTH - x - 1;
            break;
        case 2:
            // 180 degree rotation, invert x and y, then shift y around for height.
            x = WIDTH - x - 1;
            y = HEIGHT - y - 1;
            x -= (w - 1);
            break;
        case 3:
            // 270 degree rotation, swap x & y for rotation,
            // then invert y and adjust y for w (not to become h)
            bSwap = true;
            LCD_swap(x, y);
            y = HEIGHT - y - 1;
            y -= (w - 1);
            break;
    }

    if (bSwap)
        drawFastVLineInternal(x, y, w, color);
    else
        drawFastHLineInternal(x, y, w, color);
}

/*!
    @brief  Draw a vertical line. This is also invoked by the Adafruit_GFX
            library in generating many higher-level graphics primitives.
    @param  x
            Column of display -- 0 at left to (screen width -1) at right.
    @param  y
            Topmost row -- 0 at top to (screen height - 1) at bottom.
    @param  h
            Height of line, in pixels.
    @param  color
            Line color, one of: BLACK, WHITE or INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void GFX_disLCD::writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
    bool bSwap = false;
    switch (getRotation()) {
        case 1:
            // 90 degree rotation, swap x & y for rotation,
            // then invert x and adjust x for h (now to become w)
            bSwap = true;
            LCD_swap(x, y);
            x = WIDTH - x - 1;
            x -= (h - 1);
            break;
        case 2:
            // 180 degree rotation, invert x and y, then shift y around for height.
            x = WIDTH - x - 1;
            y = HEIGHT - y - 1;
            y -= (h - 1);
            break;
        case 3:
            // 270 degree rotation, swap x & y for rotation, then invert y
            bSwap = true;
            LCD_swap(x, y);
            y = HEIGHT - y - 1;
            break;
    }

    if (bSwap)
        drawFastHLineInternal(x, y, h, color);
    else
        drawFastVLineInternal(x, y, h, color);
}

void GFX_disLCD::setRotation(uint8_t r) {
    GFX_dis::setRotation(r);
    switch (_rotation) {
        case 7:
        case 5:
        case 3:
        case 1:
            _width  = HEIGHT;
            _height = WIDTH;
            break;
        case 6:
        case 4:
        case 2:
        default: // case 0:
            _width  = WIDTH;
            _height = HEIGHT;
            break;
    }

    _max_x = _width - 1;  ///< x zero base bound
    _max_y = _height - 1; ///< y zero base bound
}

} // namespace GFX
